/*
    TwoPageView image viewer
    Copyright (C) 2006 Japan Manoretimedia Lab.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

namespace TwoPageView
{
    public partial class Form1 : Form
    {
        class ThumbnailPictureBox : PictureBox
        {
            public ThumbnailPictureBox(string fullName, Bitmap image, Size originalWH)
                : base()
            {
                bExist = true;
                this.originalWH = originalWH;
                thumbnailImage = image;
                pictureNumber = -1;
                toolTip = null;
                if (null != fullName) {
                    Text = fullName;
                    string toolTipText = string.Format(System.Globalization.CultureInfo.InvariantCulture,
                        "{0}\n{1}x{2}", fullName, originalWH.Width, originalWH.Height);
                    toolTip = new ToolTip();
                    toolTip.SetToolTip(this, toolTipText);
                }
                Paint += new PaintEventHandler(PaintEventHandler);
            }

            // we do not use PictureBox.Image member, use thumbnailImage instead and 
            // render image in custom paint event handler for faster rendering
            private Bitmap thumbnailImage = null;
            public Bitmap ThumbnailImage
            {
                get { return thumbnailImage; }
            }

            private void PaintEventHandler(object sender, PaintEventArgs e)
            {
                if (thumbnailImage != null) {
                    //e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.NearestNeighbor;
                    e.Graphics.DrawImage(thumbnailImage, 0, 0, Width, Height);
                }
            }

            public void Clear()
            {
                Visible = false;

                if (null != thumbnailImage) {
                    thumbnailImage.Dispose();
                    thumbnailImage = null;
                }

                if (null != toolTip) {
                    toolTip.RemoveAll();
                    toolTip.Dispose();
                    toolTip = null;
                }
                if (null != ContextMenuStrip) {
                    ContextMenuStrip = null;
                }
            }

            // Used by Thumbnail scroll list
            private Size originalWH;
            public Size OriginalWH
            {
                get { return originalWH; }
            }
            // Used by Thumbnail scroll list
            private int pictureNumber;
            public int PictureNumber
            {
                get { return pictureNumber; }
                set { pictureNumber = value; }
            }

            private ToolTip toolTip;

            private bool bExist;
            public bool Exist
            {
                get { return bExist; }
                set { bExist = value; }
            }

            private bool bAddedToControl;
            public bool AddedToControl
            {
                get { return bAddedToControl; }
                set { bAddedToControl = value; }
            }

            public bool IsPortlait() 
            {
                if (null == thumbnailImage) {
                    return true;
                }
                if (thumbnailImage.Width <= thumbnailImage.Height) {
                    return true;
                }
                return false;
            }
        };

        private const int ZOOM_MAX = 4;
        private System.Collections.Generic.List<ThumbnailPictureBox> thumbnailGridList = new System.Collections.Generic.List<ThumbnailPictureBox>();
        private System.Collections.Generic.List<ThumbnailPictureBox> thumbnailScrollList = new System.Collections.Generic.List<ThumbnailPictureBox>();
        private System.Collections.Hashtable thumbnailFileNameHash = new System.Collections.Hashtable();
        /// <summary>
        /// readerLock: set when reading thumbnailPictureBoxList element (not needed when only reading thumbnailPictureBoxList.Count)
        /// writeLock: set when writing thumbnailPictureBoxList
        /// </summary>
        private System.Threading.ReaderWriterLock thumbnailPictureBoxListLock = new System.Threading.ReaderWriterLock();
        private int selectedImageNo;
        private Size thumbSizePortlait = new Size(200, 282);
        private Size thumbSize = new Size(200, 282);
        private System.Resources.ResourceManager rm;
        private BackgroundDirectoryIterator backgroundThumbnailLoader;
        private BackgroundDirectoryIterator backgroundThumbnailCacheCreator;
        private BackgroundWorker backgroundRecursiveThumbnailCacheCreator;
        private DirectoryOrZipDirInfo selectedDirInfo = null;
        public DirectoryOrZipDirInfo SelectedDirInfo
        {
            get { return selectedDirInfo; }
        }

        private DirectoryOrZipDirInfo droppedDirInfo = null;
        enum Mode {
            TwoPage,
            Thumb,
            OnePage,
        };
        private Mode mode;
        private UserPreference userPreference;
        enum ImageListImageId
        {
            Drive,
            NetDrive,
            FolderClosed,
            FolderOpen,
            ZipFolderClosed,
            ZipFolderOpen,
            PdfFolderClosed,
            PdfFolderOpen,
        }
        private enum PageLayout
        {
            Portrait,
            Landscape,
        };
        private PageLayout pageLayout;
        private int zoom;
        private void ToggleZoom()
        {
            ++zoom;
            if (ZOOM_MAX < zoom) {
                zoom = 1;
            }
        }
        private FormWindowState lastWindowState;
        private Image imReturnToTheListView;
        private ToolTip toolTipReturnToTheListView;
        private System.EventHandler panel1_ResizeEventHandler;
        private Image pictureBox0Image;
        private Image pictureBox1Image;
        private FileSystemWatcher watcher = new FileSystemWatcher();
        private ThumbnailCache thumbnailCache = new ThumbnailCache();
        private static FolderBrowserDialog fbd = new FolderBrowserDialog();
        private MyUtil myUtil = new MyUtil();

        ThumbnailPictureBox ThumbnailPictureBoxListPictureBoxFindByFullName(string fullName)
        {
            ThumbnailPictureBox rv = null;
            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            if (thumbnailFileNameHash.ContainsKey(fullName)) {
                rv = (ThumbnailPictureBox)thumbnailFileNameHash[fullName];
            }
            thumbnailPictureBoxListLock.ReleaseReaderLock();
            return rv;
        }

        // splitContainer1.Panel1: TreeView
        // splitContainer1.Panel2: Thumbnail list
        // splitContainer2.Panel1: Thumbnail scroll list
        // splitContainer2.Panel2: Twopage disp
        private void ThumbnailPictureBoxListClear()
        {
            Console.WriteLine("D: ThumbnailPictureBoxListClear()");
            SuspendLayout();
            thumbnailPictureBoxListLock.AcquireWriterLock(System.Threading.Timeout.Infinite);
            ImageSafeDisposeAndSetNull(ref pictureBox0Image);
            ImageSafeDisposeAndSetNull(ref pictureBox1Image);

            splitContainer1.Panel2.Visible = false;
            splitContainer1.Panel2.Controls.Clear();
            for (int i=thumbnailGridList.Count-1; 0<=i; --i) {
                ThumbnailPictureBox pb = thumbnailGridList[i];
                pb.Clear();
            }
            thumbnailGridList.Clear();
            splitContainer1.Panel2.Visible = true;

            splitContainer2.Panel1.Visible = false;
            splitContainer2.Panel1.Controls.Clear();
            for (int i=thumbnailScrollList.Count - 1; 0 <= i; --i) {
                ThumbnailPictureBox pb = thumbnailScrollList[i];
                pb.Clear();
            }
            thumbnailScrollList.Clear();
            splitContainer2.Panel1.Visible = true;

            thumbnailFileNameHash.Clear();
            thumbnailPictureBoxListLock.ReleaseWriterLock();
            ResumeLayout();
        }

        private void ThumbnailPictureBoxListAddSafe(ThumbnailPictureBox a)
        {
            thumbnailPictureBoxListLock.AcquireWriterLock(System.Threading.Timeout.Infinite);
            a.ContextMenuStrip = contextMenuStrip1;
            thumbnailGridList.Add(a);
            thumbnailFileNameHash.Add(a.Text, a);
            thumbnailPictureBoxListLock.ReleaseWriterLock();
        }

        private void ThumbnailPictureBoxListPictureExistFlagResetSafe()
        {
            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            foreach (ThumbnailPictureBox pb in thumbnailGridList) {
                pb.Exist = false;
            }
            thumbnailPictureBoxListLock.ReleaseReaderLock();
        }

        // @return true: renumbered, false: nothing changed
        private bool ThumbnailPictureBoxListRefresh()
        {
            bool bChanged = false;
            thumbnailPictureBoxListLock.AcquireWriterLock(System.Threading.Timeout.Infinite);
            System.Collections.Generic.List<ThumbnailPictureBox> removeList = new System.Collections.Generic.List<ThumbnailPictureBox>();
            foreach (ThumbnailPictureBox pb in thumbnailGridList) {
                if (!pb.Exist) {
                    removeList.Add(pb);
                    bChanged = true;
                } 
            }
            foreach (ThumbnailPictureBox pb in removeList) {
                if (pictureBox0Image == pb.Image) {
                    ImageSafeDisposeAndSetNull(ref pictureBox0Image);
                }
                if (pictureBox1Image == pb.Image) {
                    ImageSafeDisposeAndSetNull(ref pictureBox1Image);
                }
                if (pb.AddedToControl) {
                    splitContainer1.Panel2.Controls.Remove(pb);
                }
                pb.Clear();
                thumbnailGridList.Remove(pb);
                thumbnailFileNameHash.Remove(pb.Text);
            }
            thumbnailPictureBoxListLock.ReleaseWriterLock();
            ThumbnailScrollListUpdateSafe();
            return bChanged;
        }

        private void ThumbnailScrollListUpdateSafe()
        {
            // add Thumbnail scroll pictureboxes
            splitContainer2.Panel1.Controls.Clear();
            int n = 0;

            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            for (int i=thumbnailScrollList.Count - 1; 0 <= i; --i) {
                ThumbnailPictureBox pb = thumbnailScrollList[i];
                pb.Clear();
            }
            thumbnailScrollList.Clear();

            foreach (ThumbnailPictureBox pb in thumbnailGridList) {
                ThumbnailPictureBox pb2 = new ThumbnailPictureBox(null, new Bitmap(pb.ThumbnailImage), pb.OriginalWH);
                pb2.SizeMode = PictureBoxSizeMode.StretchImage;
                pb2.Top = 0;
                pb2.Left = (DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty)
                    ? n * thumbSize.Width
                    : (thumbnailGridList.Count - n - 1) * thumbSize.Width;
                pb2.Left -= splitContainer2.Panel1.AutoScrollPosition.X;
                pb2.BackColor = userPreference.BackColor.Color();
                pb2.ClientSize = pb.ClientSize;
                pb2.PictureNumber = n;
                pb2.Visible = true;
                pb2.MouseClick += new MouseEventHandler(ThumbnailPicturebox_Click);
                thumbnailScrollList.Add(pb2);
                splitContainer2.Panel1.Controls.Add(pb2);
                ++n;
            }
            thumbnailPictureBoxListLock.ReleaseReaderLock();
        }

        /// @return not found:-1
        private int ThumbnailPictureBoxListThumbnailNumberGetSafe(ThumbnailPictureBox a)
        {
            int result = -1;
            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            int i = 0;
            foreach (ThumbnailPictureBox pb in thumbnailGridList) {
                if (pb.Equals(a)) {
                    result = i;
                }
                ++i;
            }
            thumbnailPictureBoxListLock.ReleaseReaderLock();
            return result;
        }

        public Form1(DirectoryOrZipDirInfo dirInfo)
        {
            InitializeComponent();
            rm = TwoPageView.Properties.Resources.ResourceManager; 
            // rm = new System.Resources.ResourceManager("TwoPageView.Resource1", typeof(Form1).Assembly);
            userPreference = UserPreferenceIO.LoadFromFile();
            thumbnailCache.LoadFromFile(userPreference.ThumbnailCacheFolder);
            pageLayout = PageLayout.Portrait;
            selectedDirInfo = dirInfo;
            if (null != dirInfo && dirInfo.IsDragDropDir) {
                dirInfo.FullName = rm.GetString("DroppedItems"); //< not clean code... we need the better solution.
                droppedDirInfo = dirInfo;
            }
            zoom = 1;
            lastWindowState = WindowState;
            imReturnToTheListView = imageList2.Images[0];
            ModeChange(Mode.Thumb);
            pictureBox0.MouseClick += new MouseEventHandler(Picturebox0_Click);
            pictureBox0.MouseDoubleClick += new MouseEventHandler(Picturebox0_Click);
            pictureBox0.Paint += new PaintEventHandler(PictureBox0_PaintEventHandler);
            pictureBox0.Image = null;
            pictureBox1.MouseClick += new MouseEventHandler(Picturebox1_Click);
            pictureBox1.MouseDoubleClick += new MouseEventHandler(Picturebox1_Click);
            pictureBox1.Paint += new PaintEventHandler(PictureBox1_PaintEventHandler);
            pictureBox1.Image = null;
            backgroundThumbnailLoader = new BackgroundDirectoryIterator(System.Environment.ProcessorCount, new DoWorkEventHandler(OneThumbnailLoad_DoWork),
                new ProgressChangedEventHandler(BackgroundThumbnailLoader_ProgressChanged),
                new RunWorkerCompletedEventHandler(BackgroundThumbnailLoader_RunWorkerCompleted));
            backgroundThumbnailCacheCreator = new BackgroundDirectoryIterator(System.Environment.ProcessorCount, new DoWorkEventHandler(OneThumbnailCacheCreate_DoWork),
                new ProgressChangedEventHandler(ThumbnailCacheCreate_ProgressChanged),
                new RunWorkerCompletedEventHandler(ThumbnailCacheCreate_RunWorkerCompleted));
            backgroundRecursiveThumbnailCacheCreator = new BackgroundWorker();
            backgroundRecursiveThumbnailCacheCreator.WorkerReportsProgress = true;
            backgroundRecursiveThumbnailCacheCreator.WorkerSupportsCancellation = true;
            backgroundRecursiveThumbnailCacheCreator.DoWork += new DoWorkEventHandler(BackgroundRecursiveThumbnailCacheCreator_DoWork);
            backgroundRecursiveThumbnailCacheCreator.ProgressChanged += new ProgressChangedEventHandler(BackgroundRecursiveThumbnailCacheCreator_ProgressChanged);
            backgroundRecursiveThumbnailCacheCreator.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundRecursiveThumbnailCacheCreator_RunWorkerCompleted);
            ToolTip toolTip1 = new ToolTip();
            toolTip1.SetToolTip(this, rm.GetString("RightClickToDispMenu"));
            toolTip1.SetToolTip(splitContainer1.Panel2, rm.GetString("RightClickToDispMenu"));
            toolTipReturnToTheListView = new ToolTip();
            splitContainer2.Visible = false;
            splitContainer1.Dock = DockStyle.Fill;
            splitContainer2.Dock = DockStyle.Fill;
            splitContainer1.Panel2.KeyUp += new KeyEventHandler(Form1_KeyUp);
            splitContainer2.Panel1MinSize = 0;
            splitContainer2.SplitterDistance = 0;
            
            slideshowTimer.Tick += new System.EventHandler(SlideshowTimer_Tick);
            watcher.NotifyFilter = NotifyFilters.LastWrite;
            watcher.Created += new FileSystemEventHandler(Watcher_Changed);
            watcher.Changed += new FileSystemEventHandler(Watcher_Changed);
            watcher.Deleted += new FileSystemEventHandler(Watcher_Changed);
            TreeViewUpdateDriveNodes();
            this.DragEnter += new DragEventHandler(Form1_DragEnter);
            this.DragDrop += new DragEventHandler(Form1_DragDrop);
        }

        delegate void FolderUpdatedDelegate();

        private void FolderUpdated()
        {
            if (bUpdating) {
                nextTask.Set(NextTaskType.UpdateImageFileOnFolder, selectedDirInfo);
            } else {
                StartUpdateImageFilesOnFolder(selectedDirInfo);
            }
        }

        // Watcher_Changed is called from the non-UI thread but FolderUpdate must be called from the UI thread
        void Watcher_Changed(object sender, FileSystemEventArgs e)
        {
            Invoke(new FolderUpdatedDelegate(FolderUpdated));
        }

        // We don't use pictureBox0.Image because we want to draw images with custom InterpolationModeProperty
        private void PictureBox0_PaintEventHandler(object sender, PaintEventArgs e)
        {
            if (pictureBox0Image != null) {
                e.Graphics.InterpolationMode = userPreference.InterpolationModeProperty;
                e.Graphics.DrawImage(pictureBox0Image, 0, 0, pictureBox0.Width, pictureBox0.Height);
            }
        }

        private void PictureBox1_PaintEventHandler(object sender, PaintEventArgs e)
        {
            if (pictureBox1Image != null) {
                e.Graphics.InterpolationMode = userPreference.InterpolationModeProperty;
                e.Graphics.DrawImage(pictureBox1Image, 0, 0, pictureBox1.Width, pictureBox1.Height);
            }
        }

        private void UpdateStatusBarText(string statusBarText)
        {
            if (userPreference.DisplayStatusBar) {
                toolStripStatusLabel1.Text = statusBarText;
            } else {
                Text = statusBarText;
            }
        }

        private void UpdateThumbSize()
        {
            thumbSizePortlait.Width = userPreference.ThumbnailSize;
            thumbSizePortlait.Height = (int)(userPreference.ThumbnailSize * 1.41f);
            thumbSize = thumbSizePortlait;
            if (PageLayout.Landscape == pageLayout) {
                thumbSize.Width = thumbSizePortlait.Height;
                thumbSize.Height = thumbSizePortlait.Width;
            }
        }

        private void UpdateUIFromUserPreference()
        {
            {
                bookmarksOnMenubarToolStripMenuItem.DropDownItems.Clear();
                bookmarksOnMenubarToolStripMenuItem.DropDownItems.Add(addThisFolderToolStripMenuItemM);
                bookmarksOnMenubarToolStripMenuItem.DropDownItems.Add(editBookmarksToolStripMenuItemM);
                bookmarksOnMenubarToolStripMenuItem.DropDownItems.Add(new ToolStripSeparator());
                string[] bookmarkList = userPreference.BookmarkList;
                foreach (string s in bookmarkList) {
                    ToolStripMenuItem newMenuItem = new ToolStripMenuItem();
                    newMenuItem.Name = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}toolStripMenuItem", s);
                    newMenuItem.Text = s;
                    newMenuItem.Click += new System.EventHandler(ToolStripMenuItemBookmarkMenuItem_Click);
                    bookmarksOnMenubarToolStripMenuItem.DropDownItems.Add(newMenuItem);
                }
            }
            {
                bookmarksToolStripMenuItemC.DropDownItems.Clear();
                bookmarksToolStripMenuItemC.DropDownItems.Add(addThisFolderToolStripMenuItemC);
                bookmarksToolStripMenuItemC.DropDownItems.Add(editBookmarksToolStripMenuItemC);
                bookmarksToolStripMenuItemC.DropDownItems.Add(new ToolStripSeparator());
                string[] bookmarkList = userPreference.BookmarkList;
                foreach (string s in bookmarkList) {
                    ToolStripMenuItem newMenuItem = new ToolStripMenuItem();
                    newMenuItem.Name = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}toolStripMenuItem", s);
                    newMenuItem.Text = s;
                    newMenuItem.Click += new System.EventHandler(ToolStripMenuItemBookmarkMenuItem_Click);
                    bookmarksToolStripMenuItemC.DropDownItems.Add(newMenuItem);
                }
            }
            statusStrip1.Visible = userPreference.DisplayStatusBar;
            panel1.BackColor = userPreference.BackColor.Color();
            pictureBox0.BackColor = userPreference.BackColor.Color();
            pictureBox1.BackColor = userPreference.BackColor.Color();

            switch (userPreference.PageViewModeSelectionProperty) {
            case PageViewModeSelection.Always1PageHorizontal:
            case PageViewModeSelection.Always1PageHorizontal270:
                pageLayout = PageLayout.Landscape;
                break;
            case PageViewModeSelection.Always1PageVertical:
            case PageViewModeSelection.Always1PageVertical270:
                pageLayout = PageLayout.Portrait;
                break;
            }

            UpdateThumbSize();
            Action_FormFullscreenChange(userPreference.FullScreen);
        }

        private void ModeChange(Mode m)
        {
            if (mode == m) {
                return;
            }
            Console.WriteLine("D: ModeChange() {0} ==> {1}", mode.ToString(), m.ToString());
            mode = m;
            ToolStripMenuStatusUpdate();
        }

        private void MenuItemKeyStringSet(ToolStripMenuItem mi, ShortcutKey t)
        {
            mi.ShortcutKeyDisplayString = userPreference.ShortcutKeyDisplayStringGet(t);
            mi.ShowShortcutKeys = true;
        }

        private void ToolStripMenuStatusUpdate()
        {
            contextMenuStrip1.SuspendLayout();
            menuStrip1.SuspendLayout();
            MenuItemKeyStringSet(openFolderToolStripMenuItemM, ShortcutKey.OpenFolder);
            MenuItemKeyStringSet(reloadFolderToolStripMenuItemM, ShortcutKey.Reload);
            listViewModeToolStripMenuItemC.Checked = Mode.Thumb == mode;
            listViewModeToolStripMenuItemM.Checked = Mode.Thumb == mode;
            listViewModeToolStripMenuItemC.Enabled = Mode.Thumb != mode;
            listViewModeToolStripMenuItemM.Enabled = Mode.Thumb != mode;
            MenuItemKeyStringSet(listViewModeToolStripMenuItemC, ShortcutKey.ListViewMode);
            MenuItemKeyStringSet(listViewModeToolStripMenuItemM, ShortcutKey.ListViewMode);
            onePageViewModeToolStripMenuItemC.Checked = Mode.OnePage == mode;
            onePageViewModeToolStripMenuItemM.Checked = Mode.OnePage == mode;
            onePageViewModeToolStripMenuItemC.Enabled = Mode.OnePage != mode;
            onePageViewModeToolStripMenuItemM.Enabled = Mode.OnePage != mode;
            twoPageViewModeToolStripMenuItemC.Checked = Mode.TwoPage == mode;
            twoPageViewModeToolStripMenuItemM.Checked = Mode.TwoPage == mode;
            twoPageViewModeToolStripMenuItemC.Enabled = Mode.TwoPage != mode;
            twoPageViewModeToolStripMenuItemM.Enabled = Mode.TwoPage != mode;
            nextPageToolStripMenuItemC.Enabled = Mode.Thumb != mode;
            nextPageToolStripMenuItemM.Enabled = Mode.Thumb != mode;
            MenuItemKeyStringSet(nextPageToolStripMenuItemC, ShortcutKey.NextPage);
            MenuItemKeyStringSet(nextPageToolStripMenuItemM, ShortcutKey.NextPage);
            prevPageToolStripMenuItemC.Enabled = Mode.Thumb != mode;
            prevPageToolStripMenuItemM.Enabled = Mode.Thumb != mode;
            MenuItemKeyStringSet(prevPageToolStripMenuItemC, ShortcutKey.PrevPage);
            MenuItemKeyStringSet(prevPageToolStripMenuItemM, ShortcutKey.PrevPage);
            backOnePageToolStripMenuItemC.Enabled = Mode.TwoPage == mode;
            advanceOnePageToolStripMenuItemM.Enabled = Mode.TwoPage == mode;
            MenuItemKeyStringSet(backOnePageToolStripMenuItemC, ShortcutKey.AdvanceOnePage);
            MenuItemKeyStringSet(advanceOnePageToolStripMenuItemM, ShortcutKey.AdvanceOnePage);
            slideshowToolStripMenuItemM.Enabled = 0 != thumbnailGridList.Count;
            slideshowToolStripMenuItemM.Checked = slideshowTimer.Enabled;
            MenuItemKeyStringSet(slideshowToolStripMenuItemM, ShortcutKey.Slideshow);
            leftToRightDocumentDirectionToolStripMenuItemC.Checked = DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty;
            leftToRightDocumentDirectionToolStripMenuItemM.Checked = DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty;
            MenuItemKeyStringSet(leftToRightDocumentDirectionToolStripMenuItemC, ShortcutKey.ChangeDocumentDirection);
            MenuItemKeyStringSet(leftToRightDocumentDirectionToolStripMenuItemM, ShortcutKey.ChangeDocumentDirection);
            toggleFullscreenToolStripMenuItemC.Checked = userPreference.FullScreen;
            toggleFullscreenToolStripMenuItemM.Checked = userPreference.FullScreen;
            MenuItemKeyStringSet(toggleFullscreenToolStripMenuItemC, ShortcutKey.ToggleFullscreen);
            MenuItemKeyStringSet(toggleFullscreenToolStripMenuItemM, ShortcutKey.ToggleFullscreen);
            zoom1xToolStripMenuItemC.Checked = 1 == zoom;
            zoom1xToolStripMenuItemM.Checked = 1 == zoom;
            zoom2xToolStripMenuItemC.Checked = 2 == zoom;
            zoom2xToolStripMenuItemM.Checked = 2 == zoom;
            zoom3xToolStripMenuItemC.Checked = 3 == zoom;
            zoom3xToolStripMenuItemM.Checked = 3 == zoom;
            zoom4xToolStripMenuItemC.Checked = 4 == zoom;
            zoom4xToolStripMenuItemM.Checked = 4 == zoom;
            displayStatusBarToolStripMenuItemC.Checked = userPreference.DisplayStatusBar;
            displayStatusBarToolStripMenuItemM.Checked = userPreference.DisplayStatusBar;
            MenuItemKeyStringSet(preferenceToolStripMenuItemM, ShortcutKey.PreferenceDialog);
            MenuItemKeyStringSet(helpToolStripMenuItemC, ShortcutKey.Help);
            MenuItemKeyStringSet(helpToolStripMenuItemM, ShortcutKey.Help);
            displayFolderTreeToolStripMenuItemM.Checked = (this.splitContainer1.SplitterDistance != 0);
            displayFolderTreeToolStripMenuItemC.Checked = (this.splitContainer1.SplitterDistance != 0);
            folderSortByNameAscToolStripMenuItem.Checked = ItemSortOrder.NameAsc == userPreference.FolderSortOrder;
            folderSortByNameDescToolStripMenuItem.Checked = ItemSortOrder.NameDesc == userPreference.FolderSortOrder;
            folderSortByLastUpdateTimeAscToolStripMenuItem.Checked = ItemSortOrder.LastUpdateTimeAsc == userPreference.FolderSortOrder;
            folderSortByLastUpdateTimeDescToolStripMenuItem.Checked = ItemSortOrder.LastUpdateTimeDesc == userPreference.FolderSortOrder;
            menuStrip1.ResumeLayout();
            contextMenuStrip1.ResumeLayout();
        }

        private void ModeChangedSetVisible()
        {
            splitContainer2.Panel2.VerticalScroll.Value = 0;
            splitContainer2.Panel2.HorizontalScroll.Value = 0;

            switch (mode) {
            case Mode.TwoPage:
                ThumbSetVisible(false);
                TwoPageSetVisible(true);
                Focus();
                break;
            case Mode.Thumb:
                TwoPageSetVisible(false);
                ThumbSetVisible(true);
                break;
            case Mode.OnePage:
                ThumbSetVisible(false);
                OnePageSetVisible(true);
                Focus();
                break;
            default:
                break;
            }
        }

        private void OnePageUpdate()
        {
            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            panel1.Resize -= panel1_ResizeEventHandler;
            SuspendLayout();
            OnePageUpdate1();
            ResumeLayout();
            panel1.Resize += panel1_ResizeEventHandler;
            thumbnailPictureBoxListLock.ReleaseReaderLock();
        }

        // must be readerlocked
        private Image ImageGetFromImageNo(int imageNo)
        {
            return BitmapLoadFromFile(thumbnailGridList[imageNo].Text);
        }

        private void ImageSafeDisposeAndSetNull(ref Image image)
        {
            if (null != image && imReturnToTheListView != image) {
                image.Dispose();
            }
            image = null;
        }

        private Bitmap BitmapLoadFromFile(string path)
        {
            Bitmap bm = null;
            try {
                using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) {
                    bm = new Bitmap(fs);
                }
                // Do not add animated GIFs (picturebox animegif support is difficult to use)
                if (1 != bm.GetFrameCount(new System.Drawing.Imaging.FrameDimension(bm.FrameDimensionsList[0]))) {
                    bm.Dispose();
                    return null;
                }

                switch (userPreference.PageViewModeSelectionProperty) {
                case PageViewModeSelection.Always1PageHorizontal:
                    if (bm.Width < bm.Height) {
                        bm.RotateFlip(RotateFlipType.Rotate90FlipNone);
                    }
                    break;
                case PageViewModeSelection.Always1PageHorizontal270:
                    if (bm.Width < bm.Height) {
                        bm.RotateFlip(RotateFlipType.Rotate270FlipNone);
                    }
                    break;
                case PageViewModeSelection.Always1PageVertical:
                    if (bm.Height < bm.Width) {
                        bm.RotateFlip(RotateFlipType.Rotate90FlipNone);
                    }
                    break;
                case PageViewModeSelection.Always1PageVertical270:
                    if (bm.Height < bm.Width) {
                        bm.RotateFlip(RotateFlipType.Rotate270FlipNone);
                    }
                    break;
                default:
                    break;
                }
            } catch (System.ArgumentException) {
            } catch (IOException) {
            } catch (System.UnauthorizedAccessException) {
            } catch (System.Exception e) {
                Console.WriteLine("D: BitmapLoadFromFile({0}) {1}", path, e);
            }
            return bm;
        }

        static Size ThumbSizeOrientationCalc(Size imageSize, Size thumbnailSize)
        {
            Size rv = thumbnailSize;
            if ((0 == (imageSize.Width / imageSize.Height)) !=
                (0 == (thumbnailSize.Width / thumbnailSize.Height))) {
                rv.Width = thumbnailSize.Height;
                rv.Height = thumbnailSize.Width;
            }
            return rv;
        }

        private bool BitmapLoadFromFileAndResample(BackgroundWorkerWithProcess bwi, string path, Size thumbSize, out Bitmap bm, out Size originalWH)
        {
                // input:
                //   filePath
                //   RotateFlipType
                //   Size.w
                //   Size.h
                //   InterpolationMode

                // output when succeeded:
                //     int bitmapSize
                //     OriginalSizeWH
                //     Bitmap
                // output when failed:
                //     int 0
            bwi.RequestSendBase64(path);
            string sRotate = "0";
            switch (userPreference.PageViewModeSelectionProperty) {
                case PageViewModeSelection.Always1PageHorizontal: sRotate = "h90"; break;
                case PageViewModeSelection.Always1PageHorizontal270: sRotate = "h270"; break;
                case PageViewModeSelection.Always1PageVertical: sRotate = "v90"; break;
                case PageViewModeSelection.Always1PageVertical270: sRotate = "v270"; break;
                default: break;
            }
            bwi.RequestSend(sRotate);
            bwi.RequestSend(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", thumbSize.Width));
            bwi.RequestSend(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}", thumbSize.Height));
            bwi.RequestSend(userPreference.ThumbnailInterpolationModeProperty.ToString());
            bwi.RequestStarted(path);

            int bitmapSize = bwi.PipeBinaryReader.ReadInt32();
            if (0 == bitmapSize) {
                bm = null;
                originalWH = new Size(0, 0);
                return false;
            }
            int w = bwi.PipeBinaryReader.ReadInt32();
            int h = bwi.PipeBinaryReader.ReadInt32();
            originalWH = new Size(w, h);
            byte[] bytes = bwi.PipeBinaryReader.ReadBytes(bitmapSize);
            MemoryStream ms = new MemoryStream(bytes);
            bm = new Bitmap(ms);
            return true;
        }

        private bool BitmapLoadFromFileOrCache(BackgroundWorkerWithProcess bwi, string actualImagePath, string thumbnailCachePath, long fileLength, Size thumbSize, out Bitmap bm, out Size originalWH)
        {
            System.DateTime dateTime = File.GetLastWriteTime(actualImagePath);
            ThumbnailCacheEntry entry = thumbnailCache.Find(thumbnailCachePath, dateTime, fileLength);
            if (null != entry) {
                bm = entry.ThumbnailBitmap;
                originalWH = entry.OriginalWH;
                return true;
            }
            if (!BitmapLoadFromFileAndResample(bwi, actualImagePath, thumbSize, out bm, out originalWH))
            {
                return false;
            }
            thumbnailCache.Add(thumbnailCachePath, new ThumbnailCacheEntry(dateTime, fileLength, bm, originalWH));
            return true;
        }

        private bool ThumbnailBitmapCreate(BackgroundWorkerWithProcess bwi, string actualImagePath, string thumbnailCachePath, long fileLength, Size thumbSize)
        {
            System.DateTime dateTime = File.GetLastWriteTime(actualImagePath);
            bool bExist = thumbnailCache.IfEntryExist(thumbnailCachePath, dateTime, fileLength);
            if (bExist) {
                return true;
            }
            Bitmap bm;
            Size originalWH;
            if (BitmapLoadFromFileAndResample(bwi, actualImagePath, thumbSize, out bm, out originalWH)) {
                thumbnailCache.Add(thumbnailCachePath, new ThumbnailCacheEntry(dateTime, fileLength, bm, originalWH));
                bm.Dispose();
                bm = null;
                return true;
            }
            return false;
        }

        private static Size zeroWH = new Size(0, 0);
        private void OnePageUpdate1()
        {
            ImageSafeDisposeAndSetNull(ref pictureBox1Image);
            pictureBox1.ClientSize = zeroWH;
            pictureBox1.Visible = false;
            if (selectedImageNo < thumbnailGridList.Count) {
                ImageSafeDisposeAndSetNull(ref pictureBox0Image);
                Image imDisp = ImageGetFromImageNo(selectedImageNo);
                if (null != imDisp) {
                    pictureBox0.SizeMode = PictureBoxSizeMode.StretchImage;
                    Size wh = new Size(splitContainer2.Panel2.ClientSize.Width, splitContainer2.Panel2.ClientSize.Height);
                    if ((float)wh.Width / wh.Height < (float)imDisp.Width / imDisp.Height) {
                        wh.Height = imDisp.Height * wh.Width / imDisp.Width;
                    } else {
                        wh.Width = imDisp.Width * wh.Height / imDisp.Height;
                    }
                    wh.Width *= zoom;
                    wh.Height *= zoom;
                    pictureBox0.ClientSize = wh;
                    pictureBox0Image = imDisp;
                    pictureBox0.Left = (1 == zoom) ? splitContainer2.Panel2.ClientSize.Width / 2 - wh.Width / 2 : 0;
                    pictureBox0.Top = 0;
                    pictureBox0.Visible = true;
                    pictureBox0.Invalidate();
                    UpdateStatusBarText(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{2} {4}x {0}/{1} [{3}]", selectedImageNo + 1, thumbnailGridList.Count,
                        (DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty) ? "->" : "<-", thumbnailGridList[selectedImageNo].Text,
                        zoom));
                    return;
                }
            }
            ImageSafeDisposeAndSetNull(ref pictureBox0Image);
            pictureBox0.ClientSize = zeroWH;
            pictureBox0.Visible = false;
        }

        private void OnePageSetVisible(bool b)
        {
            SuspendLayout();
            pictureBox0.Visible = b;
            pictureBox1.Visible = false;
            splitContainer2.Visible = b;

            if (!b) {
                ImageSafeDisposeAndSetNull(ref pictureBox0Image);
            }
            ImageSafeDisposeAndSetNull(ref pictureBox1Image);
            ResumeLayout();
        }

        private void TwoPageUpdate()
        {
            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            panel1.Resize -= panel1_ResizeEventHandler;
            SuspendLayout();
            TwoPageUpdate1();
            ResumeLayout();
            panel1.Resize += panel1_ResizeEventHandler;
            thumbnailPictureBoxListLock.ReleaseReaderLock();
        }

        private void TwoPageUpdate1()
        {
            UpdateStatusBarText(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{2} {5}x {0}/{1} [{3}] [{4}]", selectedImageNo + 1,
                thumbnailGridList.Count,
                (DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty) ? "->" : "<-",
                (selectedImageNo < thumbnailGridList.Count) ? thumbnailGridList[selectedImageNo].Text : "",
                (selectedImageNo + 1 < thumbnailGridList.Count) ? thumbnailGridList[selectedImageNo + 1].Text : "",
                zoom));
            if (1 < zoom) {
                pictureBox0.Visible = false;
                pictureBox1.Visible = false;
            }
            if (selectedImageNo < thumbnailGridList.Count) {
                ImageSafeDisposeAndSetNull(ref pictureBox0Image);
                Image imDisp = ImageGetFromImageNo(selectedImageNo);
                if (null != imDisp) {
                    pictureBox0.SizeMode = PictureBoxSizeMode.StretchImage;
                    Size wh = new Size(splitContainer2.Panel2.ClientSize.Width, splitContainer2.Panel2.ClientSize.Height);
                    wh.Width /= 2;
                    if ((float)wh.Width / wh.Height < (float)imDisp.Width / imDisp.Height) {
                        wh.Height = imDisp.Height * wh.Width / imDisp.Width;
                    } else {
                        wh.Width = imDisp.Width * wh.Height / imDisp.Height;
                    }
                    wh.Width *= zoom;
                    wh.Height *= zoom;
                    pictureBox0.ClientSize = wh;
                    pictureBox0Image = imDisp;
                    if (DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty) {
                        pictureBox0.Left = (1 == zoom) ? splitContainer2.Panel2.ClientSize.Width / 2 - wh.Width : 0;
                    } else {
                        pictureBox0.Left = (1 == zoom) ? splitContainer2.Panel2.ClientSize.Width / 2 : wh.Width;
                    }
                    pictureBox0.Top = 0;
                    pictureBox0.Visible = true;
                    pictureBox0.Invalidate();
                    goto next;
                }
            }
            ImageSafeDisposeAndSetNull(ref pictureBox0Image);
            pictureBox0.ClientSize = zeroWH;
            pictureBox0.Visible = false;
        next:
            {
                Image imDisp = imReturnToTheListView;
                toolTipReturnToTheListView.RemoveAll();
                if (selectedImageNo + 1 < thumbnailGridList.Count) {
                    ImageSafeDisposeAndSetNull(ref pictureBox1Image);
                    imDisp = ImageGetFromImageNo(selectedImageNo + 1);
                    if (null == imDisp) {
                        imDisp = imReturnToTheListView;
                        toolTipReturnToTheListView.SetToolTip(pictureBox1, rm.GetString("ReturnToTheListView"));
                    }
                } else {
                    toolTipReturnToTheListView.SetToolTip(pictureBox1, rm.GetString("ReturnToTheListView"));
                }
                pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
                Size wh = new Size(splitContainer2.Panel2.ClientSize.Width, splitContainer2.Panel2.ClientSize.Height);
                wh.Width /= 2;
                if ((float)wh.Width / wh.Height < (float)imDisp.Width / imDisp.Height) {
                    wh.Height = imDisp.Height * wh.Width / imDisp.Width;
                } else {
                    wh.Width = imDisp.Width * wh.Height / imDisp.Height;
                }
                wh.Width *= zoom;
                wh.Height *= zoom;
                pictureBox1.ClientSize = wh;
                pictureBox1Image = imDisp;
                if (DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty) {
                    pictureBox1.Left = (1 == zoom) ? splitContainer2.Panel2.ClientSize.Width / 2 : pictureBox0.ClientSize.Width;
                } else {
                    pictureBox1.Left = (1 == zoom) ? splitContainer2.Panel2.ClientSize.Width / 2 - wh.Width : 0;
                    if (1 < zoom) { pictureBox0.Left = pictureBox1.ClientSize.Width; } //< FIXME: zoom code is not smart so far
                }
                pictureBox1.Top = 0;
                pictureBox1.Visible = true;
                pictureBox1.Invalidate();
            }
        }

        private void TwoPageSetVisible(bool b)
        {
            SuspendLayout();
            pictureBox0.Visible = b;
            pictureBox1.Visible = b;
            splitContainer2.Visible = b;

            if (!b) {
                ImageSafeDisposeAndSetNull(ref pictureBox0Image);
                ImageSafeDisposeAndSetNull(ref pictureBox1Image);
            }
            ResumeLayout();
        }

        // N.B. acquire writerlock before call
        private void ThumbnailPictureBoxListSort()
        {
            System.Collections.Generic.SortedList<string, ThumbnailPictureBox> mapForSort =
                new System.Collections.Generic.SortedList<string, ThumbnailPictureBox>();
            foreach (ThumbnailPictureBox pb in thumbnailGridList) {
                mapForSort.Add(pb.Text, pb);
            }
            thumbnailGridList.Clear();
            foreach (System.Collections.Generic.KeyValuePair<string, ThumbnailPictureBox> kbp in mapForSort) {
                thumbnailGridList.Add(kbp.Value);
            }
            mapForSort.Clear();
        }

        // Create and add the pictureboxes of the loaded bitmaps
        private void ThumbAdd()
        {
            SuspendLayout();
            thumbnailPictureBoxListLock.AcquireWriterLock(System.Threading.Timeout.Infinite);
            ThumbnailPictureBoxListSort();
            Size size = splitContainer1.Panel2.ClientSize;
            if (size.Width < thumbSize.Width) {
                size.Width = thumbSize.Width;
            }
            int n = 0;
            for (int y = 0; ; ++y) {
                bool bEnd = false;
                for (int x = 0; x < size.Width / thumbSize.Width; ++x) {
                    bEnd = ThumbAdd1(ref n, new Point(x, y));
                    if (bEnd) {
                        break;
                    }
                }
                if (bEnd) {
                    break;
                }
            }
            thumbnailPictureBoxListLock.ReleaseWriterLock();
            ResumeLayout();
            if (0 < thumbnailGridList.Count) {
                thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
                int nPortlait = 0;
                for (int i = 0; i < thumbnailGridList.Count; ++i) {
                    if (null != thumbnailGridList[i].ThumbnailImage
                        && thumbnailGridList[i].ThumbnailImage.Width < thumbnailGridList[i].ThumbnailImage.Height) {
                        ++nPortlait;
                    }
                }
                PageLayout newPageLayout = PageLayout.Portrait;
                if (nPortlait * 2 < thumbnailGridList.Count) {
                    newPageLayout = PageLayout.Landscape;
                }
                thumbnailPictureBoxListLock.ReleaseReaderLock();
                if (newPageLayout != pageLayout) {
                    pageLayout = newPageLayout;
                    UpdateThumbSize();
                    ThumbDispUpdate();
                }
            }
        }

        private void ThumbnailPictureBoxSetXY(ThumbnailPictureBox pb, Point pos)
        {
            int x;
            if (DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty) {
                x = pos.X * thumbSize.Width + splitContainer1.Panel2.AutoScrollPosition.X;
            } else {
                x = (splitContainer1.Panel2.ClientSize.Width / thumbSize.Width - pos.X - 1) * thumbSize.Width
                    + splitContainer1.Panel2.AutoScrollPosition.X;
            }
            int y = pos.Y * thumbSize.Height + splitContainer1.Panel2.AutoScrollPosition.Y;
            if (pb.Left != x) {
                pb.Left = x;
            }
            if (pb.Top != y) {
                pb.Top = y;
            }
        }
        
        private bool ThumbAdd1(ref int n, Point pos)
        {
            if (thumbnailGridList.Count <= n) {
                return true;
            }
            if (thumbnailGridList[n].AddedToControl) {
                ThumbnailPictureBoxSetXY(thumbnailGridList[n], pos);
                ++n;
                return false;
            }
            Size wh = new Size(thumbSize.Width, thumbSize.Height);
            Image imDisp = thumbnailGridList[n].ThumbnailImage;
            if ((float)wh.Width / wh.Height < (float)imDisp.Width / imDisp.Height) {
                wh.Height = imDisp.Height * wh.Width / imDisp.Width;
            } else {
                wh.Width = imDisp.Width * wh.Height / imDisp.Height;
            }
            ThumbnailPictureBox pb = thumbnailGridList[n];
            pb.SizeMode = PictureBoxSizeMode.StretchImage;
            ThumbnailPictureBoxSetXY(pb, pos);
            pb.BackColor = userPreference.BackColor.Color();
            pb.ClientSize = wh;
            pb.MouseClick += new MouseEventHandler(ThumbnailPicturebox_Click);
            pb.MouseEnter += new System.EventHandler(ThumbnailPictureBox_MouseEnter);
            pb.AddedToControl = true;
            pb.Visible = true;
            splitContainer1.Panel2.Controls.Add(pb);
            //Console.WriteLine("D: TA1 {0} {1} {2} {3}", n, pos.X, pos.Y, Path.GetFileName(pb.Text));
            ++n;
            return false;
        }

        private void ThumbSetVisible(bool b)
        {
            SuspendLayout();
            splitContainer1.Visible = b;
            // FIXME: we reset the scroll position. I don't know the better solution...
            if (b) { 
                splitContainer1.Panel2.AutoScroll = false;
                splitContainer1.Panel2.AutoScroll = true;
            }
            ResumeLayout();
        }

        private void ThumbDispUpdate()
        {
            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            panel1.Resize -= panel1_ResizeEventHandler;
            SuspendLayout();
            ThumbDispUpdate1();
            ResumeLayout();
            panel1.Resize += panel1_ResizeEventHandler;
            thumbnailPictureBoxListLock.ReleaseReaderLock();
        }

        private void ThumbDispUpdate1()
        {
            Console.WriteLine("D: ThumbDispUpdate1() Start");
            Size size = splitContainer1.Panel2.ClientSize;
            if (0 == size.Width || 0 == size.Height) {
                Console.WriteLine("D: ThumbDispUpdate1() End: Window is minimized");
                return;
            }
            if (size.Width < thumbSize.Width) {
                size.Width = thumbSize.Width;
            }
            int n = 0;
            for (int y = 0; ; ++y) {
                for (int x = 0; x < size.Width / thumbSize.Width; ++x) {
                    if (thumbnailGridList.Count <= n) {
                        Console.WriteLine("D: ThumbDispUpdate1() End");
                        return;
                    }
                    ThumbnailPictureBox pb = thumbnailGridList[n];
                    Image imDisp = pb.ThumbnailImage;
                    if (null == imDisp) {
                        Console.WriteLine("D: ThumbDispUpdate1() End: null == imDisp");
                        return;
                    }
                    pb.SizeMode = PictureBoxSizeMode.StretchImage;
                    {
                        Size wh = new Size(thumbSize.Width, thumbSize.Height);
                        if ((float)wh.Width / wh.Height < (float)imDisp.Width / imDisp.Height) {
                            wh.Height = imDisp.Height * wh.Width / imDisp.Width;
                        } else {
                            wh.Width = imDisp.Width * wh.Height / imDisp.Height;
                        }
                        if (pb.ClientSize.Width != wh.Width ||
                            pb.ClientSize.Height != wh.Height) {
                            pb.ClientSize = wh;
                        }
                    }
                    {
                        Point xy = new Point();
                        xy.Y = y * thumbSize.Height;
                        if (DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty) {
                            xy.X = x * thumbSize.Width;
                        } else {
                            xy.X = (size.Width / thumbSize.Width - x - 1) * thumbSize.Width;
                        }
                        if (pb.Top != xy.Y) {
                            pb.Top = xy.Y;
                        }
                        if (pb.Left != xy.X) {
                            pb.Left = xy.X;
                        }
                    }
                    ++n;
                }
            }
        }

        private void DispUpdate()
        {
            SuspendLayout();
            switch (mode) {
            case Mode.TwoPage:
                TwoPageUpdate();
                break;
            case Mode.OnePage:
                OnePageUpdate();
                break;
            default:
                break;
            }
            ResumeLayout();
            ThumbDispUpdate();
        }

        private string FullPath(TreeNode node)
        {
            return MyUtil.PathNormalize(FullPath1(node));
        }

        private string FullPath1(TreeNode node)
        {
            if (null != node.Parent) {
                System.Text.StringBuilder b = new System.Text.StringBuilder();
                return b.Append(FullPath1(node.Parent)).Append(Path.DirectorySeparatorChar).Append(node.Name).ToString();
            }
            return node.Name;
        }

        private void NodeTextSet(TreeNode node)
        {
            if (!userPreference.DisplayNumberOfThumbnailImagesInFolderTree) {
                return;
            }

            int nImage = thumbnailCache.NumberOfImageFilesInFolder(FullPath(node));
            string text;
            if (0 < nImage) {
                text = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} ({1})", node.Name, nImage);
            } else {
                text = node.Name;
            }
            if (!text.Equals(node.Text)) {
                node.Text = text;
            }
        }

        private string NodeText(string path, string name)
        {
            if (!userPreference.DisplayNumberOfThumbnailImagesInFolderTree) {
                return name;
            }

            {
                System.Text.StringBuilder b = new System.Text.StringBuilder(path);
                b.Append(Path.DirectorySeparatorChar);
                b.Append(name);
                path = MyUtil.PathNormalize(b.ToString());
            }
            int nImage = thumbnailCache.NumberOfImageFilesInFolder(path);
            if (0 < nImage) {
                return string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} ({1})", name, nImage);
            }
            return name;
        }

        private void TreeViewUpdateDriveNodes()
        {
            try {
                string[] drives = Directory.GetLogicalDrives();
                foreach (string drive in drives) {
                    if (!treeView1.Nodes.ContainsKey(drive)) {
                        if (drive.Length < 2) {
                            // root directory on UNIX... IGNORE
                            continue;
                        }
                        TreeNode node = treeView1.Nodes.Add(drive);
                        node.Name = drive;
                        NodeTextSet(node);
                        node.ImageIndex = (int)ImageListImageId.Drive;
                        node.SelectedImageIndex = (int)ImageListImageId.Drive;
                        node.Nodes.Add(rm.GetString("DummyNodeName"));
                    } else {
                        NodeTextSet(treeView1.Nodes[drive]);
                    }
                }
                {
                    string drive = rm.GetString("DroppedItems");
                    if (null != droppedDirInfo && !treeView1.Nodes.ContainsKey(drive)) {
                        TreeNode node = treeView1.Nodes.Add(drive);
                        node.Name = drive;
                        NodeTextSet(node);
                        node.ImageIndex = (int)ImageListImageId.Drive;
                        node.SelectedImageIndex = (int)ImageListImageId.Drive;
                    }
                }
            } catch (IOException) {
            }
        }
        class FilenameComparer : System.Collections.Generic.IComparer<string> {
            public int Compare(string x, string y) {
                return System.Globalization.CultureInfo.InvariantCulture.CompareInfo.Compare(x, y, System.Globalization.CompareOptions.IgnoreCase);
            }
        }
        private void TreeViewNodeSubTreesUpdate(TreeNode selectedNode, bool bDoNodeAddDelete)
        {
            try {
                NodeTextSet(selectedNode);
                if (!bDoNodeAddDelete) {
                    return;
                }
                DirectoryOrZipDirInfo selectedDir = NodePathToDirInfo(FullPath(selectedNode));
                selectedDir.Refresh();
                if (!selectedDir.Exists) {
                    selectedNode.Nodes.Clear();
                    return;
                }
                DirectoryOrZipDirInfo[] subDirInfo = selectedDir.GetDirectories();
                if (null == subDirInfo) {
                    selectedNode.Nodes.Clear();
                    return;
                }
                System.Collections.Generic.SortedList<string, DirectoryOrZipDirInfo> mapSortedByName =
                    new System.Collections.Generic.SortedList<string, DirectoryOrZipDirInfo>(new FilenameComparer());
                foreach (DirectoryOrZipDirInfo di in subDirInfo) {
                    // zip folder and folder can have the same foldername so we must do work deliberately
                    if (!mapSortedByName.ContainsKey(di.Name)) {
                        mapSortedByName.Add(di.Name, di);
                    }
                }

                // first remove unexisted nodes
                System.Collections.Generic.List<TreeNode> removeNodeList =
                    new System.Collections.Generic.List<TreeNode>();
                foreach (TreeNode n in selectedNode.Nodes) {
                    if (!mapSortedByName.ContainsKey(n.Name)) {
                        removeNodeList.Add(n);
                    }
                }
                foreach (TreeNode n in removeNodeList) {
                    selectedNode.Nodes.Remove(n);
                }
                removeNodeList.Clear();
                
                // di list sorted by display order
                System.Collections.Generic.List<DirectoryOrZipDirInfo> diSortedByDispOrder =
                    myUtil.FolderListSortedByDisplayOrder(subDirInfo, userPreference.FolderSortOrder);

                string path = FullPath(selectedNode);

                // add new nodes
                foreach (DirectoryOrZipDirInfo di in diSortedByDispOrder) {
                    bool bFound = false;
                    foreach (TreeNode n in selectedNode.Nodes) {
                        if (n.Name.Equals(di.Name)) {
                            bFound = true;
                            break;
                        }
                    }
                    if (bFound) {
                        continue;
                    }
                    TreeNode node = selectedNode.Nodes.Add(NodeText(path, di.Name));
                    node.Name = di.Name;
                    if (di.IsZipDir) {
                        node.ImageIndex = (int)ImageListImageId.ZipFolderClosed;
                        node.SelectedImageIndex = (int)ImageListImageId.ZipFolderOpen;
                    } else if (di.IsPdfDir) {
                        node.ImageIndex = (int)ImageListImageId.PdfFolderClosed;
                        node.SelectedImageIndex = (int)ImageListImageId.PdfFolderOpen;
                    } else {
                        node.ImageIndex = (int)ImageListImageId.FolderClosed;
                        node.SelectedImageIndex = (int)ImageListImageId.FolderOpen;
                    }
                    try {
                        if (di.IsSubDirExists()) {
                            node.Nodes.Add(rm.GetString("ReadingFolderInfo"));
                        }
                    } catch (System.UnauthorizedAccessException) {
                    }
                }
            } catch (IOException) {
            } catch (System.UnauthorizedAccessException) {
            }
        }

        private void TreeViewNodeSubTreeUpdate_r(TreeNode selectedNode, string path, bool bExpand, bool bDoNodeAddDelete)
        {
            TreeViewNodeSubTreesUpdate(selectedNode, bDoNodeAddDelete);
            if (bExpand) {
                selectedNode.Expand();
            }
            if (0 == path.Length) {
                return;
            }
            int delimiterPos = path.IndexOf(Path.DirectorySeparatorChar);
            if (delimiterPos < 0) {
                foreach (TreeNode childNode in selectedNode.Nodes) {
                    if (path.Equals(childNode.Name)) {
                        TreeViewNodeSubTreesUpdate(childNode, bDoNodeAddDelete);
                        break;
                    }
                }
                if (bDoNodeAddDelete) {
                    foreach (TreeNode n in selectedNode.Nodes) {
                        if (0 == string.Compare(n.Name, path, System.StringComparison.Ordinal)) {
                            treeView1.SelectedNode = n;
                            return;
                        }
                    }
                }
                return;
            }
            string folderName = path.Substring(0, delimiterPos);
            string subPathName = path.Substring(delimiterPos + 1);
            if (0 == subPathName.Length) {
                Console.WriteLine("D: TreeViewNodeSubTreeUpdate_r({0}, {1}) subPathName.Length == 0", FullPath(selectedNode), path);
                return;
            }
            foreach (TreeNode childNode in selectedNode.Nodes) {
                if (folderName.Equals(childNode.Name)) {
                    TreeViewNodeSubTreeUpdate_r(childNode, subPathName, bExpand, bDoNodeAddDelete);
                    return;
                }
            }
        }

        private void TreeViewFolderTreeUpdate(DirectoryOrZipDirInfo folderDirInfo, bool bExpand, bool bDoNodeAddDelete)
        {
            TreeViewUpdateDriveNodes();
            string folderPath = folderDirInfo.FullName;
            string driveLetter = folderPath.Substring(0, 3);
            if (System.Char.IsLetter(driveLetter[0]) && ':' == driveLetter[1] && '\\' == driveLetter[2]) {
                foreach (TreeNode n in treeView1.Nodes) {
                    if (0 == string.Compare(driveLetter, n.Name, System.StringComparison.Ordinal)) {
                        string subFolderPath = folderPath.Substring(3);
                        if (3 < folderPath.Length && '\\' == folderPath[3]) {
                            subFolderPath = subFolderPath.Substring(1);
                        }
                        TreeViewNodeSubTreeUpdate_r(n, subFolderPath, bExpand, bDoNodeAddDelete);
                        return;
                    }
                }
                return;
            }
            if ('\\' == driveLetter[0] && '\\' == driveLetter[1]) {
                string driveName = folderPath;
                int delimiterPos = driveName.IndexOf('\\', 2);
                if (delimiterPos < 0) {
                    return;
                }
                int delimiter2Pos = driveName.IndexOf('\\', delimiterPos + 1);
                if (0 <= delimiter2Pos) {
                    driveName = driveName.Substring(0, delimiter2Pos);
                }
                foreach (TreeNode n in treeView1.Nodes) {
                    if (0 == string.Compare(driveName, n.Name, System.StringComparison.Ordinal)) {
                        TreeViewNodeSubTreeUpdate_r(n, folderPath.Substring(delimiter2Pos + 1), bExpand, bDoNodeAddDelete);
                        return;
                    }
                }
                if (bDoNodeAddDelete) {
                    TreeNode node = treeView1.Nodes.Add(driveName);
                    node.Name = driveName;
                    NodeTextSet(node);
                    node.ImageIndex = (int)ImageListImageId.NetDrive;
                    node.SelectedImageIndex = (int)ImageListImageId.NetDrive;
                    if (delimiter2Pos < 0) {
                        TreeViewNodeSubTreesUpdate(node, true);
                    } else {
                        string subPathName = folderPath.Substring(delimiter2Pos + 1);
                        TreeViewNodeSubTreeUpdate_r(node, subPathName, bExpand, true);
                    }
                }
            }
            if ('/' == driveLetter[0] && '/' != driveLetter[1]) {
                string driveName = folderPath;
                int delimiterPos = driveName.IndexOf('/', 1);
                if (delimiterPos < 0) {
                    return;
                }
                int delimiter2Pos = driveName.IndexOf('/', delimiterPos + 1);
                if (0 <= delimiter2Pos) {
                    driveName = driveName.Substring(0, delimiter2Pos);
                }
                foreach (TreeNode n in treeView1.Nodes) {
                    if (0 == string.Compare(driveName,n.Name, System.StringComparison.Ordinal)) {
                        TreeViewNodeSubTreeUpdate_r(n, folderPath.Substring(delimiterPos + 1), bExpand, bDoNodeAddDelete);
                        return;
                    }
                }
                if (bDoNodeAddDelete) {
                    TreeNode node = treeView1.Nodes.Add(driveName);
                    node.Name = driveName;
                    NodeTextSet(node);
                    node.ImageIndex = (int)ImageListImageId.FolderClosed;
                    node.SelectedImageIndex = (int)ImageListImageId.FolderOpen;
                    TreeViewNodeSubTreesUpdate(node, true);
                }
            }
            string droppedItemsName = rm.GetString("DroppedItems");
            if (0 == string.Compare(droppedItemsName, 0, folderPath, 0, droppedItemsName.Length, System.StringComparison.Ordinal)) {
                TreeNode n = treeView1.Nodes.Find(droppedItemsName, false)[0];
                string subFolderPath = folderPath.Substring(droppedItemsName.Length);
                TreeViewNodeSubTreeUpdate_r(n, subFolderPath, bExpand, bDoNodeAddDelete);
                return;
            }
        }

        public void TreeViewRefresh()
        {
            SuspendLayout();
            treeView1.Nodes.Clear();
            TreeViewFolderTreeUpdate(selectedDirInfo, true, true);
            ResumeLayout();
        }

        // ###########################################################################################
        // Background worker thread for thumbnail creation and thread control

        enum NextTaskType
        {
            None,
            LoadImageFilesOnFolder,
            UpdateImageFileOnFolder,
        }
        class NextTask
        {
            public NextTaskType nextTaskType = NextTaskType.None;
            public DirectoryOrZipDirInfo dirInfo = null;
            public void Set(NextTaskType nextTaskType, DirectoryOrZipDirInfo dirInfo)
            {
                this.nextTaskType = nextTaskType;
                this.dirInfo = dirInfo;
            }
            public void Clear()
            {
                nextTaskType = NextTaskType.None;
            }
        }
        private NextTask nextTask = new NextTask();

        private bool bUpdating         = false;

        // Called from the UI thread
        private void StartLoadImageFilesOnFolder(DirectoryOrZipDirInfo d)
        {
            watcher.EnableRaisingEvents = false;
            if (backgroundThumbnailLoader.CancelFromUIThread(false)) {
                nextTask.Set(NextTaskType.LoadImageFilesOnFolder, d);
                return;
            }
            ThumbnailPictureBoxListClear();
            StartUpdateImageFilesOnFolder(d);
            try {
                if (Directory.Exists(d.FullName)) {
                    watcher.Path = d.FullName;
                    watcher.EnableRaisingEvents = true;
                    return;
                }
            } catch (FileNotFoundException) {
            }
            watcher.EnableRaisingEvents = false;
        }

        private void StartUpdateImageFilesOnFolder(DirectoryOrZipDirInfo d)
        {
            if (backgroundThumbnailLoader.CancelFromUIThread(false)) {
                nextTask.Set(NextTaskType.UpdateImageFileOnFolder, d);
                return;
            }
            bUpdating = true;
            Text = string.Format(System.Globalization.CultureInfo.InvariantCulture, rm.GetString("TitleBarText"), d.FullName);
            UpdateStatusBarText(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} {1}", rm.GetString("Loading"), d.FullName));
            toolStripProgressBar1.Value = 0;
            toolStripProgressBar1.Visible = true;
            toolStripStatusLabel2.Text = "0%";
            toolStripStatusLabel2.Visible = true;
            ToolStripMenuStatusUpdate();
            selectedDirInfo = d;
            BackgroundThumbnailLoaderStart(selectedDirInfo);
        }

        private void BackgroundThumbnailLoaderStart(DirectoryOrZipDirInfo di)
        {
            ThumbnailPictureBoxListPictureExistFlagResetSafe();
            backgroundThumbnailLoader.RunWorkerAsync(di);
        }

        // Load one file
        private void OneThumbnailLoad_DoWork(object sender, DoWorkEventArgs dwea)
        {
            BackgroundWorkerWithProcess bwi = sender as BackgroundWorkerWithProcess;

            BackgroundDirectoryIterator.DoWorkOnOneFileArg arg = (BackgroundDirectoryIterator.DoWorkOnOneFileArg)dwea.Argument;
            //try {
                if (arg.isDragDropDir) {
                    ThumbnailPictureBoxCreateAndAdd(bwi, arg.fi.FullName, arg.fi.FullName, arg.fi.Length);
                } else {
                    ThumbnailPictureBoxCreateAndAdd(bwi, arg.fi.FullName, arg.dFullName + Path.DirectorySeparatorChar + arg.fi.Name, arg.fi.Length);
                }
            //} catch (System.Exception e) {
            //    Console.WriteLine("D: OneThumbnailLoad_DoWork exception {0}", e.GetType());
            //}
        }

        // called from ZipDirectoryFile
        private bool ProgressReportAndQueryCancelCallback(string actualImagePath, string thumbnailCachePath, long fileLength, int progressPercentage)
        {
            MyTriBool tb;            
            tb = backgroundThumbnailLoader.QueryCancel();
            switch (tb) {
                case MyTriBool.True:
                    return true;
                case MyTriBool.False:
                    return false;
                case MyTriBool.Indeterminate:
                default:
                    break;
            }

            tb = backgroundThumbnailCacheCreator.QueryCancel();
            switch (tb) {
                case MyTriBool.True:
                    return true;
                case MyTriBool.False:
                    return false;
                case MyTriBool.Indeterminate:
                default:
                    break;
            }
            return false;
        }

        // Called from the UI thread via the backgroundWorker event callback
        private void BackgroundThumbnailLoader_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            switch (mode) {
                case Mode.OnePage:
                    if (null == pictureBox1Image) {
                        OnePageUpdate();
                        TwoPageSetVisible(true);
                    }
                    break;
                case Mode.TwoPage:
                    if (null == pictureBox0Image ||
                        pictureBox1Image == imReturnToTheListView) {
                        TwoPageUpdate();
                        TwoPageSetVisible(true);
                    }
                    break;
                case Mode.Thumb:
                    //UpdateStatusBarText(string.Format(Globalization.CultureInfo.InvariantCulture, "{0} {1}", rm.GetString("Loading"), selectedDirInfo.FullName));
                    break;
            }
            toolStripProgressBar1.Value = e.ProgressPercentage;
            if (userPreference.DisplayStatusBar) {
                toolStripStatusLabel2.Text = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}%", e.ProgressPercentage);
            }
            ThumbAdd();
            ToolStripMenuStatusUpdate();
        }

        // Called from the UI thread via the backgroundWorker event callback
        private void BackgroundThumbnailLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            BackgroundWorker bw = (BackgroundWorker)sender;
            UpdateStatusBarText(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} {2}/{3} {1}", rm.GetString("LoadCompleted"), selectedDirInfo.FullName, selectedImageNo + 1, thumbnailGridList.Count));
            toolStripProgressBar1.Visible = false;
            toolStripStatusLabel2.Visible = false;
            if (ThumbnailPictureBoxListRefresh()) {
                ThumbDispUpdate();
            }
            bUpdating = false;

            if (e.Error != null) {
                Console.WriteLine("D: BackgroundWorker_RunWorkerCompleted({0}) Error! {1}", selectedDirInfo.FullName, e.Error.Message);
            } else if (e.Cancelled) {
                Console.WriteLine("D: BackgroundWorker_RunWorkerCompleted({0}) Cancelled", selectedDirInfo.FullName);
                UpdateStatusBarText(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} {1}", rm.GetString("LoadCancelled"), selectedDirInfo.FullName));
            } else {
                Console.WriteLine("D: BackgroundWorker_RunWorkerCompleted({0}) Succeeded!", selectedDirInfo.FullName);
                SuspendLayout();
                TreeViewFolderTreeUpdate(selectedDirInfo, false, true);
                ResumeLayout();
            }

            if (bw.IsBusy) {
                // FIXME: This situation occurs only on Mono...
                return;
            }

            switch (nextTask.nextTaskType) {
                case NextTaskType.LoadImageFilesOnFolder:
                    nextTask.Clear();
                    StartLoadImageFilesOnFolder(nextTask.dirInfo);
                    break;
                case NextTaskType.UpdateImageFileOnFolder:
                    nextTask.Clear();
                    StartUpdateImageFilesOnFolder(nextTask.dirInfo);
                    break;
                case NextTaskType.None:
                    break;
            }
        }

        private void ThumbnailPictureBoxCreateAndAdd(BackgroundWorkerWithProcess bwi, string actualImagePath, string thumbnailCachePath, long fileLength)
        {
            ThumbnailPictureBox pb = ThumbnailPictureBoxListPictureBoxFindByFullName(actualImagePath);
            if (null != pb) {
                pb.Exist = true;
                return;
            }
            Bitmap bm;
            Size originalWH;
            if (!BitmapLoadFromFileOrCache(bwi, actualImagePath, thumbnailCachePath, fileLength, thumbSize, out bm, out originalWH)) {
                return;
            }
            ThumbnailPictureBoxListAddSafe(new ThumbnailPictureBox(actualImagePath, bm, originalWH));
        }

        // ###########################################################################################
        // Background thumbnail creation

        bool bThumbnailCacheCreateRun = false;

        private void OneThumbnailCacheCreate_DoWork(object sender, DoWorkEventArgs dwea)
        {
            BackgroundWorkerWithProcess bwi = sender as BackgroundWorkerWithProcess;
            BackgroundDirectoryIterator.DoWorkOnOneFileArg arg = (BackgroundDirectoryIterator.DoWorkOnOneFileArg)dwea.Argument;
            FileInfo fi = arg.fi;
            ThumbnailBitmapCreate(bwi, fi.FullName, arg.dFullName + Path.DirectorySeparatorChar + fi.Name, fi.Length, thumbSize);           
        }

        private void ThumbnailCacheCreate_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
        }

        private void ThumbnailCacheCreate_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            bThumbnailCacheCreateRun = false;
        }

        private void ThumbnailCacheCreateRecursive(BackgroundWorker bw, string path)
        {
            try {
                DirectoryOrZipDirInfo d = DirectoryOrZipDirInfo.Factory(path);
                DirectoryOrZipDirInfo[] subDirs = d.GetDirectories();
                if (null != subDirs) {
                    foreach (DirectoryOrZipDirInfo subDir in subDirs) {
                        if (bw.CancellationPending) {
                            return;
                        }
                        ThumbnailCacheCreateRecursive(bw, subDir.FullName);
                    }
                }

                while (bThumbnailCacheCreateRun) {
                    System.Threading.Thread.Sleep(100);
                }

                bThumbnailCacheCreateRun = true;
                backgroundThumbnailCacheCreator.RunWorkerAsync(d);

                do {
                    if (bw.CancellationPending) {
                        backgroundThumbnailCacheCreator.CancelFromUIThread(false);
                        break;
                    }
                    System.Threading.Thread.Sleep(1);
                } while (bThumbnailCacheCreateRun);

                bw.ReportProgress(0, path);
                System.GC.Collect();
            } catch (System.UnauthorizedAccessException) {
            } catch (System.Exception ex) {
                Console.WriteLine(ex);
            }
        }

        private void BackgroundRecursiveThumbnailCacheCreator_DoWork(object sender, DoWorkEventArgs e)
        {
            // Lowest priority causes OutOfMemoryException so we must call GC.Collect periodically
            //System.Threading.Thread.CurrentThread.Priority = System.Threading.ThreadPriority.Lowest;

            BackgroundWorker bw = (BackgroundWorker)sender;
            ThumbnailCacheCreateRecursive(bw, (string)e.Argument);
            if (backgroundRecursiveThumbnailCacheCreator.CancellationPending) {
                Console.WriteLine("D: BackgroundRecursiveThumbnailCreater_DoWork() Cancelled");
                e.Cancel = true;
            }
        }

        private void BackgroundRecursiveThumbnailCacheCreator_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            SuspendLayout();
            TreeViewFolderTreeUpdate(NodePathToDirInfo((string)e.UserState), false, false);
            ResumeLayout();
        }

        private void BackgroundRecursiveThumbnailCacheCreator_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
        }

        public void BackgroundRecursiveThumbnailCacheCreationStop()
        {
            if (backgroundRecursiveThumbnailCacheCreator.IsBusy) {
                backgroundRecursiveThumbnailCacheCreator.CancelAsync();
            }
        }

        public void BackgroundRecursiveThumbnailCacheCreationStart(string path)
        {
            if (backgroundRecursiveThumbnailCacheCreator.IsBusy) {
                return;
            }
            backgroundRecursiveThumbnailCacheCreator.RunWorkerAsync(path);
        }
        
        public bool BackgroundRecursiveThumbnailCreaterIsBusy()
        {
            return backgroundRecursiveThumbnailCacheCreator.IsBusy;
        }

        private bool BackgroundRecursiveThumbnailCreaterCancelFromUIThread(bool bBlock)
        {
            Console.WriteLine("D: BackgroundRecursiveThumbnailCreaterCancelFromUIThread(bBlock={0})", bBlock);
            if (!backgroundRecursiveThumbnailCacheCreator.IsBusy) {
                return false;
            }
            backgroundRecursiveThumbnailCacheCreator.CancelAsync();
            if (bBlock) {
                while (backgroundRecursiveThumbnailCacheCreator.IsBusy) {
                    Application.DoEvents();
                    System.Threading.Thread.Sleep(100);
                }
            }
            return backgroundRecursiveThumbnailCacheCreator.IsBusy;
        }

        // ###########################################################################################
        // Action event

        private bool Action_SelectFolderAndLoadImageFilesOnFolder()
        {
            {
                fbd.Description = rm.GetString("SpecifyImageFolder");
                if (selectedDirInfo.FullName.Length != 0) {
                    fbd.SelectedPath = selectedDirInfo.FullName;
                }
                DialogResult dr = fbd.ShowDialog();
                if (dr != DialogResult.OK) {
                    return false;
                }
                selectedDirInfo = DirectoryOrZipDirInfo.Factory(fbd.SelectedPath);
            }
            selectedImageNo = 0;
            Console.WriteLine("D: Action_SelectFolderAndLoadImageFilesOnFolder() SelectedPath={0}", selectedDirInfo.FullName);
            StartLoadImageFilesOnFolder(selectedDirInfo);
            SuspendLayout();
            TreeViewFolderTreeUpdate(selectedDirInfo, true, true);
            ResumeLayout();
            return true;
        }

        // Must be readerlocked before call!
        private void SwitchToSuitableDispMode()
        {
            switch (userPreference.PageViewModeSelectionProperty) {
            case PageViewModeSelection.Always1Page:
            case PageViewModeSelection.Always1PageHorizontal:
            case PageViewModeSelection.Always1PageHorizontal270:
            case PageViewModeSelection.Always1PageVertical:
            case PageViewModeSelection.Always1PageVertical270:
                if (Mode.TwoPage == mode) {
                    ModeChange(Mode.OnePage);
                }
                return;
            default:
                break;
            }
            if (selectedImageNo < thumbnailGridList.Count) {
                if (Mode.TwoPage == mode) {
                    if (!thumbnailGridList[selectedImageNo].IsPortlait()) {
                        ModeChange(Mode.OnePage);
                    }
                }
                if (Mode.OnePage == mode) {
                    if (thumbnailGridList[selectedImageNo].IsPortlait()) {
                        ModeChange(Mode.TwoPage);
                    }
                }
            }
        }

        private bool Action_GotoNextPage()
        {
            Console.WriteLine("D: Action_GotoNextPage() {0}", selectedImageNo);
            if (Mode.Thumb == mode) {
                return false;
            }
            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            bool zoomUpdatedTo1x = false;
            if (zoom != 1) {
                zoom = 1;
                zoomUpdatedTo1x = true;
            }
            int advanceVal = 1;
            if (Mode.TwoPage == mode) {
                if (selectedImageNo + 1 < thumbnailGridList.Count) {
                    if (thumbnailGridList[selectedImageNo + 1].IsPortlait()) {
                        advanceVal = 2;
                    }
                }
            }
            selectedImageNo += advanceVal;
            if (thumbnailGridList.Count <= selectedImageNo) {
                selectedImageNo -= advanceVal;
                Action_ChangeToThumbDispMode();
            }
            SwitchToSuitableDispMode();
            thumbnailPictureBoxListLock.ReleaseReaderLock();
            return Action_PageUpdate(zoomUpdatedTo1x);
        }

        private void Action_GotoPrevPage()
        {
            Console.WriteLine("D: Action_GotoPrevPage {0}", selectedImageNo);
            if (Mode.Thumb == mode) {
                return;
            }
            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            bool zoomUpdatedTo1x = false;
            if (zoom != 1) {
                zoom = 1;
                zoomUpdatedTo1x = true;
            }
            int advanceVal = 1;
            if (PageViewModeSelection.AutoSelect == userPreference.PageViewModeSelectionProperty) {
                if (2 <= selectedImageNo && selectedImageNo <= thumbnailGridList.Count) {
                    if (thumbnailGridList[selectedImageNo - 1].IsPortlait()
                        && thumbnailGridList[selectedImageNo - 2].IsPortlait()) {
                        advanceVal = 2;
                    }
                }
            }
            selectedImageNo -= advanceVal;
            if (selectedImageNo < 0) {
                selectedImageNo = 0;
                Action_ChangeToThumbDispMode();
            }
            SwitchToSuitableDispMode();
            thumbnailPictureBoxListLock.ReleaseReaderLock();
            Action_PageUpdate(zoomUpdatedTo1x);
        }

        private void Action_BackOnePage()
        {
            if (mode != Mode.TwoPage) {
                return;
            }
            bool zoomUpdatedTo1x = false;
            if (zoom != 1) {
                zoom = 1;
                zoomUpdatedTo1x = true;
            }
            selectedImageNo -= 1;
            if (selectedImageNo < 1) {
                selectedImageNo = 1;
            }
            Action_PageUpdate(zoomUpdatedTo1x);
        }

        private void Action_AdvanceOnePage()
        {
            if (mode != Mode.TwoPage) {
                return;
            }
            bool zoomUpdatedTo1x = false;
            if (zoom != 1) {
                zoom = 1;
                zoomUpdatedTo1x = true;
            }
            selectedImageNo += 1;
            Action_PageUpdate(zoomUpdatedTo1x);
        }

        private void Action_ChangeToOnePageDispMode()
        {
            ModeChange(Mode.OnePage);
            ModeChangedSetVisible();
            OnePageUpdate();
        }

        private void Action_ChangeToTwoPageDispMode()
        {
            ModeChange(Mode.TwoPage);
            ModeChangedSetVisible();
            TwoPageUpdate();
        }

        private bool CheckIfSelectedImagePortlaitSafe()
        {
            bool result;
            thumbnailPictureBoxListLock.AcquireReaderLock(System.Threading.Timeout.Infinite);
            result = thumbnailGridList[selectedImageNo].IsPortlait();
            thumbnailPictureBoxListLock.ReleaseReaderLock();
            return result;
        }

        private void Action_ChangeToPageDispMode()
        {
            Console.WriteLine("D: Action_ChangeToPageDispMode()");
            switch (userPreference.PageViewModeSelectionProperty) {
            case PageViewModeSelection.Always1Page:
            case PageViewModeSelection.Always1PageHorizontal:
            case PageViewModeSelection.Always1PageHorizontal270:
            case PageViewModeSelection.Always1PageVertical:
            case PageViewModeSelection.Always1PageVertical270:
                Action_ChangeToOnePageDispMode();
                break;
            default:
                if (CheckIfSelectedImagePortlaitSafe()) {
                    Action_ChangeToTwoPageDispMode();
                } else {
                    Action_ChangeToOnePageDispMode();
                }
                break;
            }
        }

        private void Action_ChangeToThumbDispMode()
        {
            ModeChange(Mode.Thumb);
            ModeChangedSetVisible();
        }

        private void Action_FlipLandscapePortlait()
        {
            if (mode != Mode.Thumb) {
                return;
            }
            if (PageLayout.Portrait == pageLayout) {
                pageLayout = PageLayout.Landscape;
            } else {
                pageLayout = PageLayout.Portrait;
            }
            UpdateThumbSize();
            ThumbDispUpdate();
            ToolStripMenuStatusUpdate();
        }

        private void Action_FormFullscreenChange(bool b)
        {
            Console.WriteLine("D: Action_FormFullscreenChange({0})", b);
            SuspendLayout();
            if (b) {
                statusStrip1.Visible = false;
                if (FormBorderStyle.None != FormBorderStyle) {
                    lastWindowState = WindowState;
                    menuStrip1.Visible = false;
                    WindowState = FormWindowState.Normal;
                    FormBorderStyle = FormBorderStyle.None;
                    WindowState = FormWindowState.Maximized;
                }
            } else {
                statusStrip1.Visible = userPreference.DisplayStatusBar;
                if (FormBorderStyle.None == FormBorderStyle) {
                    menuStrip1.Visible = true;
                    WindowState = FormWindowState.Normal;
                    FormBorderStyle = FormBorderStyle.Sizable;
                    if (FormWindowState.Maximized == lastWindowState) {
                        WindowState = FormWindowState.Maximized;
                    }
                }
            }
            ResumeLayout();
            ToolStripMenuStatusUpdate();
        }

        private void Action_ChangeDocumentDirection()
        {
            switch (userPreference.DocumentArrangeDirectionProperty) {
            case DocumentArrangeDirection.LeftToRight:
                userPreference.DocumentArrangeDirectionProperty = DocumentArrangeDirection.RightToLeft;
                break;
            case DocumentArrangeDirection.RightToLeft:
                userPreference.DocumentArrangeDirectionProperty = DocumentArrangeDirection.LeftToRight;
                break;
            default:
                System.Diagnostics.Debug.Assert(false);
                break;
            }
            ToolStripMenuStatusUpdate();
            ThumbnailScrollListUpdateSafe();
            DispUpdate();
        }

        private void Action_SlideshowStart()
        {
            Console.WriteLine("D: Action_SlideshowStart()");
            if (slideshowTimer.Enabled) {
                Action_SlideshowStop();
            }
            if (zoom != 1) {
                zoom = 1;
                Action_PageUpdate(true);
            }
            Action_ChangeToPageDispMode();
            slideshowTimer.Interval = userPreference.SlideshowAdvanceMSec;
            Action_FormFullscreenChange(true);
            slideshowTimer.Start();
            ToolStripMenuStatusUpdate();
        }

        private bool Action_SlideshowStop()
        {
            Console.WriteLine("D: Action_SlideshowStop()");
            if (!slideshowTimer.Enabled) {
                return false;
            }
            slideshowTimer.Stop();
            ToolStripMenuStatusUpdate();
            Action_FormFullscreenChange(userPreference.FullScreen);
            return true;
        }

        private void Action_ToggleSlideshow()
        {
            if (slideshowTimer.Enabled) {
                Action_SlideshowStop();
                return;
            }
            if (0 == thumbnailGridList.Count) {
                return;
            }
            if (thumbnailGridList.Count <= selectedImageNo) {
                selectedImageNo = 0;
            }
            Action_SlideshowStart();
        }

        private void Action_PreferenceDialogOpen()
        {
            using (FormUserPreference f = new FormUserPreference(userPreference, rm.GetString("HelpFilename"))) {
                DialogResult rv = f.ShowDialog(this);
                if (DialogResult.OK == rv) {
                    UserPreferenceIO.SaveToFile(userPreference);
                    UpdateUIFromUserPreference();
                    ToolStripMenuStatusUpdate();
                    ThumbDispUpdate();
                    if (f.ThumbnailMustUpdate) {
                        thumbnailCache.FileInit();
                        StartLoadImageFilesOnFolder(selectedDirInfo);
                    }
                }
            }
        }

        private void Action_ShowHelp()
        {
            Help.ShowHelp(this, rm.GetString("HelpFilename"));
        }

        /// <summary>
        /// page update
        /// </summary>
        /// <param name="zoomUpdatedTo1x">true if zoom updated to 1x (turn off scrollbar)</param>
        /// <returns></returns>
        private bool Action_PageUpdate(bool zoomUpdatedTo1x)
        {
            splitContainer2.Panel2.VerticalScroll.Value = 0;
            splitContainer2.Panel2.HorizontalScroll.Value = 0;
            if (zoomUpdatedTo1x) {
                splitContainer2.Visible = false;
            }
            bool rv = false;
            switch (mode) {
            case Mode.TwoPage:
                TwoPageUpdate();
                rv = true;
                break;
            case Mode.OnePage:
                OnePageUpdate();
                rv = true;
                break;
            default:
                rv = false;
                break;
            }
            if (zoomUpdatedTo1x) {
                splitContainer2.Visible = true;
            }
            ToolStripMenuStatusUpdate();
            return rv;
        }

        private void Action_ToggleStatusBarVisible()
        {
            userPreference.DisplayStatusBar = !userPreference.DisplayStatusBar;
            statusStrip1.Visible = userPreference.DisplayStatusBar;
            if (userPreference.DisplayStatusBar) {
                Text = string.Format(System.Globalization.CultureInfo.InvariantCulture, rm.GetString("TitleBarText"), selectedDirInfo.FullName);
                toolStripStatusLabel1.Text = rm.GetString("ToolStripDefault");
            }
            ToolStripMenuStatusUpdate();
        }

        // ###########################################################################################
        // Event handlers

        // Called when the application startup is completed.
        private void Form1_Load(object sender, System.EventArgs e)
        {
            DirectoryOrZipDirInfo.ProgressReportAndQueryCancelDelegateSet(
                new ProgressReportAndQueryCancel(ProgressReportAndQueryCancelCallback));

            panel1_ResizeEventHandler = new System.EventHandler(Panel1_Resize);
            panel1.Resize += panel1_ResizeEventHandler;
            splitContainer2.Panel2.Resize += new System.EventHandler(splitContainer2_Panel2_Resize);
            MouseWheel += new MouseEventHandler(Form1_MouseWheel);
            menuStrip1.CanOverflow = true;
            toolStripProgressBar1.Visible = false;
            toolStripStatusLabel2.Visible = false;
            UpdateUIFromUserPreference();

            // Workaround of statusstrip padding bug
            using (Graphics g = Graphics.FromHwnd(Handle)) {
                Console.WriteLine("D: {0}, {1}", g.DpiX, g.DpiY);
                statusStrip1.Padding = new Padding(1, 0, (int)(13 * 120 / g.DpiX), 0);
            }

            if (null == selectedDirInfo && userPreference.OpenFolderWhenStartup) {
                selectedDirInfo = DirectoryOrZipDirInfo.Factory(userPreference.OpenFolderPath);
            }
            if (null != selectedDirInfo) {
                StartLoadImageFilesOnFolder(selectedDirInfo);
                SuspendLayout();
                TreeViewFolderTreeUpdate(selectedDirInfo, true, true);
                ResumeLayout();
            }
        }

        private void Form1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
                e.Effect = DragDropEffects.Copy;
            } else {
                e.Effect = DragDropEffects.None;
            }
        }

        private void Form1_DragDrop(object sender, DragEventArgs e)
        {
            string[] paths = e.Data.GetData(DataFormats.FileDrop) as string[];
            Console.WriteLine("D: Form1_DragDrop() {0}", paths.Length);
            for (int i = 0; i < paths.Length; ++i) {
                Console.WriteLine("   {0}", paths[i]);
            }
            DirectoryOrZipDirInfo d = DirectoryOrZipDirInfo.Factory(paths);
            if (null != d && d.IsDragDropDir) { // not clean code... we need the better solution.
                d.FullName = rm.GetString("DroppedItems");
            }
            droppedDirInfo = d;
            selectedImageNo = 0;
            StartLoadImageFilesOnFolder(d);
        }

        private void Panel1_Resize(object sender, System.EventArgs e)
        {
            Console.WriteLine("D: Panel1_Resize({0}, {1})", ClientSize.Width, ClientSize.Height);
            DispUpdate();
        }

        private void Form1_KeyUp(object sender, KeyEventArgs e)
        {
            Console.WriteLine("D: Form1_KeyUp({0})", e.KeyCode);
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.NextPage))) {
                Action_GotoNextPage();
                return;
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.PrevPage))) {
                Action_GotoPrevPage();
                return;
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.AdvanceOnePage))) {
                Action_AdvanceOnePage();
                return;
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.ChangeDocumentDirection))) {
                Action_ChangeDocumentDirection();
                return;
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.Reload))) {
                StartLoadImageFilesOnFolder(selectedDirInfo);
                return;
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.ChangeDispMode))) {
                switch (mode) {
                case Mode.OnePage:
                case Mode.TwoPage:
                    Action_ChangeToThumbDispMode();
                    break;
                case Mode.Thumb:
                    if (0 < thumbnailGridList.Count) {
                        if (thumbnailGridList.Count <= selectedImageNo) {
                            selectedImageNo = 0;
                        }
                        Action_ChangeToPageDispMode();
                    }
                    break;
                default:
                    System.Diagnostics.Debug.Assert(false);
                    break;
                }
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.Slideshow))) {
                Action_ToggleSlideshow();
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.ToggleFullscreen))) {
                userPreference.FullScreen = !userPreference.FullScreen;
                Action_FormFullscreenChange(userPreference.FullScreen);
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.OpenFolder))) {
                Action_SelectFolderAndLoadImageFilesOnFolder();
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.PreferenceDialog))) {
                Action_PreferenceDialogOpen();
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.Help))) {
                Action_ShowHelp();
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.Zoom))) {
                ToggleZoom();
                Action_PageUpdate(true);
            }
            if (e.KeyCode.ToString().Equals(userPreference.ShortcutKeyGet(ShortcutKey.ListViewMode))) {
                Action_ChangeToThumbDispMode();
            }
        }

        private void Form1_MouseWheel(object sender, MouseEventArgs e)
        {
            Console.WriteLine("D: Form_MouseWheel({0})", e.Delta);
            if (e.Delta < 0) {
                Action_GotoNextPage();
            }
            if (0 < e.Delta) {
                Action_GotoPrevPage();
            }
        }

        private void SlideshowTimer_Tick(object myObject,
                                            System.EventArgs myEventArgs)
        {
            Console.WriteLine("D: SlideshowTimer_Tick()");
            if (!Action_GotoNextPage()) {
                Action_SlideshowStop();
            }
        }

        private void ThumbnailPicturebox_Click(object sender, MouseEventArgs e)
        {
            Console.WriteLine("D: ThumbPicturebox_Click({0})", e.Button.ToString());
            if (MouseButtons.Left == e.Button) {
                ThumbnailPictureBox pb = sender as ThumbnailPictureBox;
                if (Mode.Thumb == mode) {
                    // splitContainer1.Panel2 Thumbnail list
                    if (null != pb) {
                        selectedImageNo = ThumbnailPictureBoxListThumbnailNumberGetSafe(pb);
                    }
                    if (selectedImageNo < 0) {
                        selectedImageNo = 0;
                    }
                    Console.WriteLine("D: ThumbPicturebox_Click() pictureNumber=={0}", selectedImageNo);
                    Action_ChangeToPageDispMode();
                } else {
                    // splitContainer2.Panel1 Thumbnail scroll list
                    if (null != pb) {
                        selectedImageNo = pb.PictureNumber;
                    }
                    Console.WriteLine("D: ThumbPicturebox_Click() pictureNumber=={0}", selectedImageNo);
                    Action_ChangeToPageDispMode();
                }
            }
        }

        void ThumbnailPictureBox_MouseEnter(object sender, System.EventArgs e)
        {
            Panel2Focus();
        }

        private void Picturebox0_Click(object sender, MouseEventArgs e)
        {
            Console.WriteLine("D: Picturebox0_Click({0})", e.Button.ToString());
            if (MouseButtons.Middle == e.Button) {
                ToggleZoom();
                Action_PageUpdate(true);
                return;
            }
            if (MouseButtons.Left == e.Button) {
                if (Action_SlideshowStop()) {
                    return;
                }
                if (Mode.OnePage == mode) {
                    if (0 == 2 * e.X / pictureBox0.Width) {
                        if (DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty) {
                            Action_GotoPrevPage();
                        } else {
                            Action_GotoNextPage();
                        }
                    } else {
                        if (DocumentArrangeDirection.LeftToRight == userPreference.DocumentArrangeDirectionProperty) {
                            Action_GotoNextPage();
                        } else {
                            Action_GotoPrevPage();
                        }
                    }
                    return;
                }
                if (Mode.TwoPage == mode) {
                    Action_GotoPrevPage();
                }
            }
        }

        private void Picturebox1_Click(object sender, MouseEventArgs e)
        {
            Console.WriteLine("D: Picturebox1_Click({0})", e.Button.ToString());
            if (MouseButtons.Middle == e.Button) {
                ToggleZoom();
                Action_PageUpdate(true);
                return;
            }
            if (MouseButtons.Left == e.Button) {
                Action_SlideshowStop();
                Action_GotoNextPage();
            }
        }

        private void ToolStripMenuItemOpenFolder_Click(object sender, System.EventArgs e)
        {
            Action_SelectFolderAndLoadImageFilesOnFolder();
        }

        private void ToolStripMenuItemReloadFolder_Click(object sender, System.EventArgs e)
        {
            StartLoadImageFilesOnFolder(selectedDirInfo);
        }

        /// <summary>
        /// Dispose all known objects before exit
        /// </summary>
        private void CleanupBeforeExit()
        {
            Console.WriteLine("D: CleanupBeforeExit() start");
            watcher.EnableRaisingEvents = false;
            backgroundThumbnailLoader.CancelFromUIThread(true);
            BackgroundRecursiveThumbnailCreaterCancelFromUIThread(true);
            ThumbnailPictureBoxListClear();
            Console.WriteLine("D: CleanupBeforeExit() end");
        }

        private void ToolStripMenuItemExit_Click(object sender, System.EventArgs e)
        {
            CleanupBeforeExit();

            Console.WriteLine("D: ToolStripMenuItemExit_Click() calling Exit...");
            Application.Exit();
        }

        private void ToolStripMenuItemThumbnailMode_Click(object sender, System.EventArgs e)
        {
            Action_ChangeToThumbDispMode();
        }

        private void ToolStripMenuItemTwoPageViewMode_Click(object sender, System.EventArgs e)
        {
            ThumbnailPictureBox pb = contextMenuStrip1.SourceControl as ThumbnailPictureBox;
            if (null != pb) {
                selectedImageNo = ThumbnailPictureBoxListThumbnailNumberGetSafe(pb);
                if (selectedImageNo < 0) {
                    selectedImageNo = 0;
                }
            }
            Action_ChangeToTwoPageDispMode();
        }

        private void ToolStripMenuItemOnePageViewMode_Click(object sender, System.EventArgs e)
        {
            ThumbnailPictureBox pb = contextMenuStrip1.SourceControl as ThumbnailPictureBox;
            if (null != pb) {
                selectedImageNo = ThumbnailPictureBoxListThumbnailNumberGetSafe(pb);
                if (selectedImageNo < 0) {
                    selectedImageNo = 0;
                }
            }
            Action_ChangeToOnePageDispMode();
        }

        private void ToolStripMenuItemNextPage_Click(object sender, System.EventArgs e)
        {
            Action_GotoNextPage();
        }

        private void ToolStripMenuItemPrevPage_Click(object sender, System.EventArgs e)
        {
            Action_GotoPrevPage();
        }

        private void ToolStripMenuItemAdvance1Page_Click(object sender, System.EventArgs e)
        {
            Action_AdvanceOnePage();
        }

        private void ToolStripMenuItemLeftToRightDocumentDirection_Click(object sender, System.EventArgs e)
        {
            Action_ChangeDocumentDirection();
        }

        private void ToolStripMenuItemFullscreen_Click(object sender, System.EventArgs e)
        {
            userPreference.FullScreen = !userPreference.FullScreen;
            Action_FormFullscreenChange(userPreference.FullScreen);
        }

        private void ToolStripMenuItemSlideShow_Click(object sender, System.EventArgs e)
        {
            Action_ToggleSlideshow();
        }

        private void ToolStripMenuItemZoom1x_Click(object sender, System.EventArgs e)
        {
            zoom = 1;
            Action_PageUpdate(true);
        }

        private void ToolStripMenuItemZoom2x_Click(object sender, System.EventArgs e)
        {
            zoom = 2;
            Action_PageUpdate(true);
        }

        private void ToolStripMenuItemZoom3x_Click(object sender, System.EventArgs e)
        {
            zoom = 3;
            Action_PageUpdate(true);
        }

        private void ToolStripMenuItemZoom4x_Click(object sender, System.EventArgs e)
        {
            zoom = 4;
            Action_PageUpdate(true);
        }

        private void ToolStripMenuItemPreference_Click(object sender, System.EventArgs e)
        {
            Action_PreferenceDialogOpen();
        }

        private void ToolStripMenuItemHelp_Click(object sender, System.EventArgs e)
        {
            Action_ShowHelp();
        }

        private void ToolStripMenuItemVersionInfo_Click(object sender, System.EventArgs e)
        {
            string s = string.Format(System.Globalization.CultureInfo.InvariantCulture, 
                rm.GetString("VersionInfo"), AssemblyVersion);
            MessageBox.Show(s, s, 
                MessageBoxButtons.OK, MessageBoxIcon.Information,
                MessageBoxDefaultButton.Button1, 0);
        }
        
        private static string AssemblyVersion
        {
            get { return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); }
        }
        
        private void ToolStripMenuItemDisplayStatusBar_Click(object sender, System.EventArgs e)
        {
            Action_ToggleStatusBarVisible();
        }

        private void ToolStripMenuItemAddThisFolder_Click(object sender, System.EventArgs e)
        {
            if (null == selectedDirInfo || selectedDirInfo.IsDragDropDir) {
                return;
            }
            string[] bookmarkList = userPreference.BookmarkList;
            foreach (string s in bookmarkList) {
                if (s.Equals(selectedDirInfo.FullName)) {
                    return;
                }
            }
            userPreference.BookmarkAdd(selectedDirInfo.FullName);
            UpdateUIFromUserPreference();

            UserPreferenceIO.SaveToFile(userPreference);
        }

        private DirectoryOrZipDirInfo NodePathToDirInfo(string path)
        {
            string droppedItemsName = rm.GetString("DroppedItems");
            if (0 == string.Compare(droppedItemsName, 0, path, 0, droppedItemsName.Length, System.StringComparison.Ordinal)) {
                return droppedDirInfo;
            }
            return DirectoryOrZipDirInfo.Factory(path);
        }

        private void ToolStripMenuItemBookmarkMenuItem_Click(object sender, System.EventArgs e)
        {
            ToolStripMenuItem senderMI = sender as ToolStripMenuItem;
            if (null == senderMI) {
                return;
            }
            selectedImageNo = 0;
            StartLoadImageFilesOnFolder(NodePathToDirInfo(senderMI.Text));
        }

        private void ToolStripMenuItemEditBookmarks_Click(object sender, System.EventArgs e)
        {
            using (FormEditBookmarks f = new FormEditBookmarks(userPreference)) {
                DialogResult rv = f.ShowDialog(this);
                if (DialogResult.OK == rv) {
                    UserPreferenceIO.SaveToFile(userPreference);
                    UpdateUIFromUserPreference();
                }
            }
        }

        private void SplitContainer1_Panel2_Resize(object sender, System.EventArgs e)
        {
            ThumbDispUpdate();
        }

        private void SplitContainer1_Panel2_MouseEnter(object sender, System.EventArgs e)
        {
            Panel2Focus();
        }

        private void Panel2Focus()
        {
            if (splitContainer1.Panel2.CanFocus) {
                splitContainer1.Panel2.Focus();
            }
        }

        private void TreeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            Console.WriteLine("D: TreeView1_BeforeExpand({0}) start", e.Node);
            SuspendLayout();
            TreeViewNodeSubTreesUpdate(e.Node, true);
            ResumeLayout();
            Console.WriteLine("D: TreeView1_BeforeExpand({0}) end", e.Node);
        }

        private void TreeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            Console.WriteLine("D: TreeView1_NodeMouseClick({0}) start", FullPath(e.Node));
            SuspendLayout();
            TreeViewNodeSubTreesUpdate(e.Node, true);
            ResumeLayout();
            selectedImageNo = 0;
            StartLoadImageFilesOnFolder(NodePathToDirInfo(FullPath(e.Node)));
            Console.WriteLine("D: TreeView1_NodeMouseClick({0}) end", FullPath(e.Node));
        }

        private void TreeView1_MouseEnter(object sender, System.EventArgs e)
        {
            if (treeView1.CanFocus) {
                treeView1.Focus();
            }
        }

        private void TreeView1_KeyUp(object sender, KeyEventArgs e)
        {
            if (Keys.Enter == e.KeyCode) {
                if (null != treeView1.SelectedNode) {
                    selectedImageNo = 0;
                    StartLoadImageFilesOnFolder(NodePathToDirInfo(FullPath(treeView1.SelectedNode)));
                }
            }
        }

        private void ToolStripMenuItemTwoPageViewWebpage_Click(object sender, System.EventArgs e)
        {
            try {
                System.Diagnostics.Process.Start("http://code.google.com/p/twopageview/");
            } catch (Win32Exception) {
            }
        }

        private void ToolStripMenuItemThumbnailCacheSettings_Click(object sender, System.EventArgs e)
        {
            using (ThumbnailCacheSettings f = new ThumbnailCacheSettings(this, thumbnailCache, backgroundThumbnailCacheCreator, userPreference)) {
                DialogResult rv = f.ShowDialog(this);
                if (DialogResult.OK == rv) {
                    UserPreferenceIO.SaveToFile(userPreference);
                }
            }
        }

        private int prevSplitterDistance = 200;
        private void ToolStripMenuItemDisplayFolderTree_Click(object sender, System.EventArgs e)
        {
            if (splitContainer1.SplitterDistance != 0) {
                splitContainer1.Panel1MinSize = 0;
                prevSplitterDistance = splitContainer1.SplitterDistance;
                splitContainer1.SplitterDistance = 0;
            } else {
                this.splitContainer1.Panel1MinSize = 25;
                this.splitContainer1.SplitterDistance = prevSplitterDistance;
            }
            ToolStripMenuStatusUpdate();
        }

        private void ToolStripMenuItemFolderSortByNameAsc_Click(object sender, System.EventArgs e)
        {
            userPreference.FolderSortOrder = ItemSortOrder.NameAsc;
            UpdateUIFromUserPreference();
            TreeViewRefresh();
        }

        private void ToolStripMenuItemFolderSortByNameDesc_Click(object sender, System.EventArgs e)
        {
            userPreference.FolderSortOrder = ItemSortOrder.NameDesc;
            UpdateUIFromUserPreference();
            TreeViewRefresh();
        }

        private void ToolStripMenuItemFolderSortByLastUpdateTimeAsc_Click(object sender, System.EventArgs e)
        {
            userPreference.FolderSortOrder = ItemSortOrder.LastUpdateTimeAsc;
            UpdateUIFromUserPreference();
            TreeViewRefresh();
        }

        private void ToolStripMenuItemFolderSortByLastUpdateTimeDesc_Click(object sender, System.EventArgs e)
        {
            userPreference.FolderSortOrder = ItemSortOrder.LastUpdateTimeDesc;
            UpdateUIFromUserPreference();
            TreeViewRefresh();
        }

        private void splitContainer2_Panel2_MouseMove(object sender, MouseEventArgs e)
        {
            //Console.WriteLine("D: splitContainer2_Panel2_MouseMove {0}, {1}", e.X, e.Y);
            if (e.Y < thumbSize.Height + splitContainer2.Panel2MinSize && 0 == splitContainer2.Panel1MinSize) {
                splitContainer2.Panel1MinSize = thumbSize.Height + splitContainer2.Panel2MinSize;
                splitContainer2.SplitterDistance = splitContainer2.Panel1MinSize;
            } else if (0 < e.Y && 0 != splitContainer2.Panel1MinSize) {
                splitContainer2.Panel1MinSize = 0;
                splitContainer2.SplitterDistance = 0;
            }
        }

        private void splitContainer2_Panel2_Resize(object sender, System.EventArgs e)
        {
            DispUpdate();
        }
    }
}
